package cn.yj.core.business.service;

import cn.yj.annotation.pagehelper.page.Page;
import cn.yj.cgs.dba.entity.Customer;
import cn.yj.cgs.dba.entity.Integral;
import cn.yj.cgs.dba.entity.IntegralWater;
import cn.yj.cgs.dba.entity.vo.IntegralWaterProgress;
import cn.yj.cgs.dba.entity.vo.InterWaterRollVo;
import cn.yj.cgs.dba.mapper.IntegralMapper;
import cn.yj.cgs.dba.mapper.IntegralWaterMapper;
import cn.yj.common.Enums;
import cn.yj.common.UUIdUtils;
import cn.yj.commons.enums.LPATTERM;
import cn.yj.commons.utils.*;
import cn.yj.config.SchoolCBDConfig;
import cn.yj.core.business.service.iservice.IIntegralService;
import cn.yj.entity.R;
import cn.yj.params.check.annotation.CheckObjectValue;
import cn.yj.params.check.annotation.KeyValue;
import cn.yj.params.check.annotation.Require;
import cn.yj.tools.exception.ServiceException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.*;

import static cn.yj.commons.utils.FilterNull.isNull;

/**
 * @author 永健
 * @since 2021-04-20 13:42
 */
@Service
public class IntegralService extends ServiceImpl<IntegralMapper, Integral> implements IIntegralService,
        ApplicationContextAware {

    @Resource
    private IntegralWaterMapper waterMapper;

    @Resource
    private SchoolCBDConfig cbdConfig;

    @Resource
    private CustomerService customerService;

    private IntegralService thisTarget;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        thisTarget = applicationContext.getBean(this.getClass());
    }

    @Override
    public SchoolCBDConfig.IntegralConfig getIntegralRules() {

        SchoolCBDConfig.IntegralConfig integralConfig = cbdConfig.getIntegralConfig();
        try {
            String fileContent = FileUtils.getFileContent("classpath:html/rule.html", this.getClass());
            integralConfig.setRuleRemark(fileContent);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return integralConfig;
    }

    static Map<String, List<Map<String, Object>>> progress = new HashMap<>();

    static {
        Map<String, Object> init = new HashMap<>();
        Map<String, Object> handing = new HashMap<>();
        Map<String, Object> success = new HashMap<>();
        Map<String, Object> fail = new HashMap<>();
        init.put("word", Enums.IntegralWater.STATUS.submit.getMsg());
        init.put("status", Enums.IntegralWater.STATUS.submit.name());
        init.put("isCurrentStatus", false);

        handing.put("word", Enums.IntegralWater.STATUS.doing.getMsg() + "\n预计3个工作日到账");
        handing.put("status", Enums.IntegralWater.STATUS.doing.name());
        handing.put("isCurrentStatus", false);


        success.put("word", Enums.IntegralWater.STATUS.success.getMsg());
        success.put("status", Enums.IntegralWater.STATUS.success.name());
        success.put("isCurrentStatus", false);

        fail.put("word", Enums.IntegralWater.STATUS.fail.getMsg());
        fail.put("status", Enums.IntegralWater.STATUS.fail.name());
        fail.put("isCurrentStatus", false);
        List<Map<String, Object>> listSuccess = new ArrayList<>();
        listSuccess.add(init);
        listSuccess.add(handing);
        listSuccess.add(success);
        List<Map<String, Object>> listFail = new ArrayList<>();
        listFail.add(init);
        listFail.add(handing);
        listFail.add(fail);
        progress.put(Enums.IntegralWater.STATUS.fail.name(), listFail);
        progress.put(Enums.IntegralWater.STATUS.success.name(), listSuccess);
        progress.put(Enums.IntegralWater.STATUS.submit.name(), listSuccess);
        progress.put(Enums.IntegralWater.STATUS.doing.name(), listSuccess);

    }

    @Override
    public IntegralWaterProgress getProgress(@Require String orderNo) {
        IntegralWater integralWater =
                waterMapper.selectOne(new QueryWrapper<IntegralWater>().lambda().eq(IntegralWater::getOrderNo,
                        orderNo));
        if (StringUtils.isNull(integralWater)) {
            throw new ServiceException(String.format("找不到积分流水信息：%s", orderNo));
        }

        IntegralWaterProgress water = new IntegralWaterProgress();
        water.setId(integralWater.getId());
        water.setOrderNo(integralWater.getOrderNo());
        water.setAmount(integralWater.getIntegral());
        water.setCreateTime(integralWater.getCreateTime());
        water.setCurrentStatus(integralWater.getStatus().name());
        water.setType(integralWater.getSource().getMsg());
        water.setMoney(integralWater.getPayAmount());
        water.setCreateTime(integralWater.getCreateTime());

        List<Map<String, Object>> maps = progress.get(integralWater.getStatus().name());
        maps.forEach(map -> {
            if (integralWater.getStatus().name().equals(map.get("status").toString())) {
                map.put("isCurrentStatus", true);
            }
        });
        water.setProgress(maps);
        return water;
    }


    @Override
    @Transactional
    public boolean updateIntegral(
            @Require String userCode, @Require BigDecimal amount, boolean isAdd,
            @Require Enums.IntegralWater.SOURCE source, String inviteCode) {
        Integral integral = baseMapper.selectIntegralByUserCodeForUpdate(userCode);
        isNull(integral, String.format("用户积分账户不存在:%s", userCode));

        // 不可小于0
        if (BigDecimalUtils.isLeftLERight(amount, BigDecimal.ZERO)) {
            throw new ServiceException(String.format("积分不可小于或等于0:%s", integral));
        }
        handlerSub(integral, amount, isAdd);
        handlerAdd(integral, amount, isAdd);

        // 流水记录
        IntegralWater integralWater = new IntegralWater();
        integralWater.setUserCode(userCode);
        integralWater.setIntegral(amount);
        integralWater.setType(isAdd? Enums.IntegralWater.TYPE.IN : Enums.IntegralWater.TYPE.OUT);
        integralWater.setOrderNo(UUIdUtils.getUUId32());
        integralWater.setPayAmount(source == Enums.IntegralWater.SOURCE.CASH_OUT? BigDecimalUtils.mul(amount,
                cbdConfig.getIntegralConfig().getMoneyOne()) : amount);
        integralWater.setSource(source);
        integralWater.setInviteCode(inviteCode);
        integralWater.setStatus(isAdd? Enums.IntegralWater.STATUS.success :
                Enums.IntegralWater.SOURCE.CASH_OUT == source? Enums.IntegralWater.STATUS.doing :
                        Enums.IntegralWater.STATUS.success);

        // 更新积分
        if (baseMapper.updateById(integral) <= Enums.Common.ZERO) {
            throw new ServiceException("更新积分失败，请稍后重试");
        }
        return waterMapper.insert(integralWater) > Enums.Common.ZERO;
    }

    private void handlerAdd(Integral integral, BigDecimal amount, boolean isAdd) {
        if (isAdd) {
            integral.setIntegral(BigDecimalUtils.add(integral.getIntegral(), amount));
        }
    }

    private void handlerSub(Integral integral, BigDecimal amount, boolean isAdd) {
        if (!isAdd) {
            if (BigDecimalUtils.isLeftLTRight(integral.getIntegral(), amount)) {
                throw new ServiceException(String.format("积分不足:当前积分:%s,使用积分:%s", integral.getIntegral(), amount));
            }
            integral.setIntegral(BigDecimalUtils.sub(integral.getIntegral(), amount));
        }
    }

    /**
     * 获取积分信息
     *
     * @param userCode 用户编码
     *
     * @return map - integral 可用积分
     * - usedIntegral 已使用
     * - currentDaySign 当天是否已经签到
     */
    @Override
    public Map<String, Object> getIntegralByUserCode(@Require String userCode) {
        Integral integral = baseMapper.selectOne(new QueryWrapper<Integral>().lambda().eq(Integral::getUserCode,
                userCode));
        if (StringUtils.isBlank(userCode)) {
            throw new ServiceException(String.format("用户不存在:%", userCode));
        }
        HashMap<String, Object> resMap = new HashMap<>();
        BigDecimal used = waterMapper.selectOutCountListByStatus(userCode, Enums.IntegralWater.TYPE.OUT.name());

        resMap.put("integral", integral.getIntegral());
        resMap.put("usedIntegral", used);
        resMap.put("currentDaySign", toDayIsSing(userCode));
        resMap.put("keepSignDay", getKeepSignDay(userCode));
        resMap.put("minCash", cbdConfig.getIntegralConfig().getMinCash());
        resMap.put("maxCash", cbdConfig.getIntegralConfig().getMaxCash());

        return resMap;
    }

    @Override
    public BigDecimal getSurplusIntegralByUserCode(@Require String userCode) {
        Integral integral = baseMapper.selectOne(new QueryWrapper<Integral>().lambda().eq(Integral::getUserCode,
                userCode));
        if (StringUtils.isBlank(userCode)) {
            throw new ServiceException(String.format("用户不存在:%", userCode));
        }
        return integral.getIntegral();
    }

    @Override
    public Page<Object> getListByUserCode(@Require Page<Object> page, @Require String userCode, @Require String type) {
        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("type", type);
        waterMapper.findList(params, page);
        return page;
    }

    private boolean toDayIsSing(String userCode) {
        // 查询参数
        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("source", Enums.IntegralWater.SOURCE.SIGN_IN.name());
        params.put("createTime", DateTimeUtils.nowDateString());
        return waterMapper.exitsByMap(params) > Enums.Common.ZERO;
    }

    @Transactional
    @Override
    public R signIn(@Require String userCode) {

        // 响应参数
        Map<String, Object> resMap = new HashMap<>();
        resMap.put("isKeepSign", false);

        // 判断流水是否签到了今天
        if (!toDayIsSing(userCode)) {
            thisTarget.updateIntegral(userCode, cbdConfig.getIntegralConfig().getSingIn(), true,
                    Enums.IntegralWater.SOURCE.SIGN_IN, null);
        }

        // 计算连续签到日期
        SignDay signDay = getKeepSignDay(userCode);

        // 是否是第15天签到 ##### 还没有签到15天的记录 #### 判断收否有15天了
        if (signDay.getSignDay() >= cbdConfig.getIntegralConfig().getKeepDay()) {
            // 查询参数
            Map<String, Object> params = new HashMap<>();
            params.put("userCode", userCode);
            params.put("source", Enums.IntegralWater.SOURCE.KEEP_SIGN.name());
            if (waterMapper.exitsByMap(params) <= Enums.Common.ZERO) {
                thisTarget.updateIntegral(userCode, cbdConfig.getIntegralConfig().getKeepSign(), true,
                        Enums.IntegralWater.SOURCE.KEEP_SIGN, null);
                resMap.put("isKeepSign", true);
                resMap.put("msg", String.format("恭喜您，您已连续签到：%s天,额外获得：%s积分",
                        cbdConfig.getIntegralConfig().getKeepDay(), cbdConfig.getIntegralConfig().getKeepSign()));
            }
        }
        resMap.put("signDay", signDay);
        return R.success(resMap);
    }

    private SignDay getKeepSignDay(String userCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("source", Enums.IntegralWater.SOURCE.SIGN_IN.name());
        List<Date> dates = waterMapper.listCreateTimeAsc(params);
        SignDay signDay = getSignDay(dates);
        return signDay;

    }

    @Override
    public List<List<String>> getSignDay(@Require String userCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("source", Enums.IntegralWater.SOURCE.KEEP_SIGN.name());
        List<Date> dates = waterMapper.listCreateTimeAsc(params);
        SignDay signDay = getSignDay(dates);
        return StringUtils.isNotNull(signDay)? signDay.getKeepDayAllList() : new ArrayList<>();
    }

    @Override
    public BigDecimal getAmountByUserCode(@Require String userCode) {
        return baseMapper.selectIntegralByUserCode(userCode);
    }


    public SignDay getSignDay(List<Date> listDates) {
        Date preDate = null;

        SignDay signDay = new SignDay();
        if (listDates.isEmpty()) {
            return signDay;
        }

        int count = Enums.Common.ONE;
        List<List<String>> keepSignDateList = new ArrayList<>();
        List<String> keepSignDate = new ArrayList<>();
        for (Date nextDate : listDates) {
            if (preDate == null) {
                preDate = nextDate;
                continue;
            }
            String preDateStr = DateTimeUtils.format(preDate, LPATTERM.YMD);
            String nextDateStr = DateTimeUtils.format(DateUtils.addDays(nextDate, -Enums.Common.ONE), LPATTERM.YMD);
            keepSignDate.add(preDateStr);
            preDate = nextDate;
            if (nextDateStr.equals(preDateStr)) {
                // 记录连续天数
                ++count;
                // 记录连续天数日期
                continue;
            }
            // 连续天数断开
            keepSignDateList.add(keepSignDate);
            // 还原从1开始
            keepSignDate = new ArrayList<>();
            count = Enums.Common.ONE;
        }
        keepSignDate.add(DateTimeUtils.format(listDates.get(listDates.size() - Enums.Common.ONE), LPATTERM.YMD));
        keepSignDateList.add(keepSignDate);

        // 拍个序 不排序默认取最新时间的
        // keepSignDateList.sort((o1, o2) -> o1.size() > o2.size()? -Enums.Common.ONE : Enums.Common.ONE);
        signDay.setKeepDayAllList(keepSignDateList);
        signDay.setSignDay(count);
        signDay.setKeepDayFinishList(count >= cbdConfig.getIntegralConfig().getKeepDay()?
                keepSignDateList.get(keepSignDate.size() - 1) : new ArrayList<>());
        return signDay;
    }

    @CheckObjectValue(keyValue = @KeyValue(type = Map.class, name = {"userCode", "amount"}))
    @Transactional
    public boolean withdrawal(Map<String, Object> params) {
        // 判断当月已经提现的人数


        String userCode = MapUtils.getStringVal(params, "userCode");

        // 是否填写了支付宝
        Customer custom = customerService.getOne(new QueryWrapper<Customer>().lambda().eq(Customer::getUserCode,
                userCode).select(Customer::getAlipayAccount));
        if (StringUtils.isNull(custom) || StringUtils.isBlank(custom.getAlipayAccount())) {
            throw new ServiceException("请先填写支付宝账户");
        }

        BigDecimal amount = new BigDecimal(MapUtils.getStringVal(params, "amount"));
        if (BigDecimalUtils.isLeftLTRight(amount, new BigDecimal(cbdConfig.getIntegralConfig().getMinCash())) || BigDecimalUtils.isLeftGTRight(amount, new BigDecimal(cbdConfig.getIntegralConfig().getMaxCash()))) {
            throw new ServiceException("积分最小提现100，最大200");
        }
        return this.updateIntegral(userCode, new BigDecimal(amount.intValue()), false,
                Enums.IntegralWater.SOURCE.CASH_OUT, null);
    }

    @Override
    public Page<InterWaterRollVo> getRollRecords(Page<InterWaterRollVo> page) {
        List<String> sources = new ArrayList<>();
        sources.add(Enums.IntegralWater.SOURCE.CASH_OUT.name());
        sources.add(Enums.IntegralWater.SOURCE.EXCHANGE.name());
        waterMapper.selectRecordsBySource(sources, page);
        return page;
    }

    public class SignDay {
        private boolean isKeepSign;
        private int signDay = Enums.Common.ONE;
        private List<List<String>> keepDayAllList;
        private List<String> keepDayFinishList;

        public boolean isKeepSign() {
            return isKeepSign;
        }

        public int getSignDay() {
            return signDay;
        }

        public SignDay setSignDay(int signDay) {
            this.signDay = signDay;
            return this;
        }

        public SignDay setKeepSign(boolean keepSign) {
            isKeepSign = keepSign;
            return this;
        }

        public List<List<String>> getKeepDayAllList() {
            return keepDayAllList;
        }

        public void setKeepDayAllList(List<List<String>> keepDayAllList) {
            this.keepDayAllList = keepDayAllList;
        }

        public List<String> getKeepDayFinishList() {
            return keepDayFinishList;
        }

        public void setKeepDayFinishList(List<String> keepDayFinishList) {
            this.keepDayFinishList = keepDayFinishList;
        }
    }
}
